// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: hyuan

#pragma once

#include <iomanip>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "coin2/base/time.h"
#include "coin2/exchange/base/api_base/asio_http_context.h"
#include "coin2/exchange/base/api_base/base_auth.h"
#include "coin2/exchange/base/api_util/api_signature.h"
#include "coin2/exchange/base/api_util/auth_key.h"
#include "coin2/exchange/base/api_util/json_writer.h"
#include "coin2/exchange/base/api_util/url_params_builder.h"

class PhemexBaseAuth {
 public:
  virtual ~PhemexBaseAuth() = default;

 protected:
  std::string GetExpiryTimeSeconds(int plus) const {
    int64_t seconds = GetCurrentTimestamp() / 1e9;
    return std::to_string(seconds + plus);
  }
};

class PhemexWsAuth : public PhemexBaseAuth {
 public:
  explicit PhemexWsAuth(const std::string& key_filepath)
      : key_(AuthKey::FromFile(key_filepath)) {
  }

  const std::string CreateSignature() const {
    std::string expiry = GetExpiryTimeSeconds(120);
    std::string signa = GetSignature(key_.secret_key(), key_.access_key(), expiry);
    std::string params = "\"API\",\"" + key_.access_key() + "\",\"" + signa + "\"," + expiry;
    return fmt::format(R"({{"id": 10000, "method": "user.auth", "params": [{}]}})", params);
  }

 protected:
  std::string GetSignature(const std::string& secret_key, const std::string& api_key,
      const std::string& expiry) const {
    if (secret_key.empty() || api_key.empty()) {
      throw std::runtime_error("[Phemex] API key and secret key are required");
    }
    std::string cred = api_key + expiry;
    return ApiSignatureUtil::GetSignatureByHexHmacSHA256(cred, secret_key);
  }

 protected:
  AuthKey key_;
};

class PhemexRestAuth : public PhemexBaseAuth, public BaseRestAuth {
 public:
  explicit PhemexRestAuth(
      const std::string& key_filepath,
      const std::string& host,
      const std::string& port,
      const std::string& bind_ip)
      : key_(AuthKey::FromFile(key_filepath)), host_(host), port_(port), bind_ip_(bind_ip) {
  }
  explicit PhemexRestAuth(
      const std::string& key_filepath,
      const std::string& host,
      int32_t port,
      const std::string& bind_ip)
      : key_(AuthKey::FromFile(key_filepath)), host_(host), port_(std::to_string(port)),
          bind_ip_(bind_ip) {
  }

  std::string GetSignature(const std::string& secret_key, const std::string& url_path,
      const std::string& query_string, const std::string& expiry, const std::string& body) const {
    if (secret_key.empty()) {
      throw std::runtime_error("[Phemex] API key and secret key are required");
    }
    std::string cred = url_path + query_string + expiry + body;
    return ApiSignatureUtil::GetSignatureByHexHmacSHA256(cred, secret_key);
  }

  std::vector<std::pair<std::string, std::string>> GetHeaderSet(
      const std::string& url_path,
      const std::string& query_string,
      const std::string& body) const {
    std::vector<std::pair<std::string, std::string>> out;
    std::string ts_str = GetExpiryTimeSeconds(60);
    std::string signa = GetSignature(key_.secret_key(), url_path, query_string, ts_str, body);
    out.push_back(std::make_pair("x-phemex-access-token", key_.access_key()));
    out.push_back(std::make_pair("x-phemex-request-signature", signa));
    out.push_back(std::make_pair("x-phemex-request-expiry", ts_str));
    return out;
  }

  AsioHttpContext* CreateRequestByGetWithSignature(
      const std::string& url_path,
      UrlParamsBuilder* builder) override {
    std::string query_string = builder->ComputeQueryString();
    std::string post_body = builder->ComputePostBody();
    CHECK(post_body.empty());
    std::string target = url_path;
    if (!query_string.empty()) {
      target += "?" + query_string;
    }

    return AsioHttpContext::NewGet(host_, port_, bind_ip_, target,
        GetHeaderSet(url_path, query_string, ""));
  }

  AsioHttpContext* CreateRequestByPostWithSignature(
      const std::string& url_path,
      UrlParamsBuilder* builder) override {
    std::string query_string = builder->ComputeQueryString();
    CHECK(query_string.empty());
    std::string post_body = builder->ComputePostBody();
    std::string target = url_path;
    if (!query_string.empty()) {
      target += "?" + query_string;
    }

    return AsioHttpContext::NewPost(host_, port_, bind_ip_, target,
        GetHeaderSet(url_path, query_string, post_body), post_body);
  }

  AsioHttpContext* CreateRequestByDeleteWithSignature(
      const std::string& url_path,
      UrlParamsBuilder* builder) override {
    std::string query_string = builder->ComputeQueryString();
    std::string post_body = builder->ComputePostBody();
    std::string target = url_path;
    if (!query_string.empty()) {
      target += "?" + query_string;
    }

    return AsioHttpContext::NewDelete(host_, port_, bind_ip_, target,
        GetHeaderSet(url_path, query_string, post_body), post_body);
  }

  AsioHttpContext* CreateRequestByPutWithSignature(
      const std::string& address,
      UrlParamsBuilder* builder) override {
    return nullptr;
  }

 protected:
  AuthKey key_;
  std::string host_;
  std::string port_;
  std::string bind_ip_;
};
